/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Marcus Britanicus (https://gitlab.com/marcusbritanicus)
 * Copyright (c) 2021 Abrar (https://gitlab.com/s96Abrar)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 **/

#include <QDebug>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <wayland-client.h>

#include "wayqt/ToplevelManager.hpp"
#include "wayqt/WayQtUtils.hpp"

#include "wayfire-toplevel-management-unstable-v1-client-protocol.h"

WQt::ToplevelManager::ToplevelManager( zwayfire_toplevel_manager_v1 *tlMgr ) {
    mObj = tlMgr;
    zwayfire_toplevel_manager_v1_add_listener( mObj, &mTlMgrListener, this );
}


WQt::ToplevelManager::~ToplevelManager() {
    zwayfire_toplevel_manager_v1_stop( mObj );
    zwayfire_toplevel_manager_v1_destroy( mObj );
}


void WQt::ToplevelManager::setup() {
    if ( mIsSetup == false ) {
        mIsSetup = true;

        for ( WQt::Toplevel *tl: mToplevels.values() ) {
            emit newToplevel( tl );
        }

        emit receivedToplevels( mToplevels.values() );
    }
}


zwayfire_toplevel_manager_v1 *WQt::ToplevelManager::get() {
    return mObj;
}


void WQt::ToplevelManager::getToplevelForUuid( uint32_t uuid ) {
    if ( mToplevels.contains( uuid ) ) {
        emit toplevel( mToplevels[ uuid ] );
    }

    else {
        zwayfire_toplevel_manager_v1_get_toplevel_for_uuid( mObj, uuid );
    }
}


void WQt::ToplevelManager::sendAllToplevels() {
    /** Request the compositor to send the toplevels */
    zwayfire_toplevel_manager_v1_toplevels( mObj );
}


WQt::Toplevels WQt::ToplevelManager::toplevels() {
    return mToplevels.values();
}


void WQt::ToplevelManager::handleNewToplevel( void *data, zwayfire_toplevel_manager_v1 *, zwayfire_toplevel_v1 *hndl, uint32_t uuid ) {
    ToplevelManager *winMgr = reinterpret_cast<ToplevelManager *>(data);

    WQt::Toplevel *handle = new WQt::Toplevel( hndl, uuid );

    winMgr->mToplevels[ uuid ] = handle;

    /** Emit this only if we have called setup() */
    if ( winMgr->mIsSetup ) {
        emit winMgr->newToplevel( handle );
    }
}


void WQt::ToplevelManager::handleToplevel( void *data, zwayfire_toplevel_manager_v1 *, zwayfire_toplevel_v1 *hndl, uint32_t uuid ) {
    ToplevelManager *winMgr = reinterpret_cast<ToplevelManager *>(data);

    WQt::Toplevel *handle = nullptr;

    for ( WQt::Toplevel *h: winMgr->mToplevels.values() ) {
        if ( h->get() == hndl ) {
            handle = h;
            emit winMgr->toplevel( handle );
        }
    }

    if ( handle == nullptr ) {
        handle = new WQt::Toplevel( hndl, uuid );

        /** Emit this only if we have called setup() */
        if ( winMgr->mIsSetup ) {
            emit winMgr->newToplevel( handle );
        }
    }

    winMgr->mPendingToplevels[ uuid ] = handle;
}


void WQt::ToplevelManager::handleFinished( void *data, zwayfire_toplevel_manager_v1 * ) {
    ToplevelManager *winMgr = reinterpret_cast<ToplevelManager *>(data);
    emit winMgr->finished();
}


void WQt::ToplevelManager::handleDone( void *data, zwayfire_toplevel_manager_v1 * ) {
    ToplevelManager *winMgr = reinterpret_cast<ToplevelManager *>(data);

    /** First time we are processing the done event */
    if ( winMgr->mFirstTime ) {
        winMgr->mFirstTime = false;
    }

    else {
        winMgr->mToplevels = winMgr->mPendingToplevels;

        /** Do not delete the objects */
        winMgr->mPendingToplevels.clear();
    }

    /** Emit this only if we have called setup() */
    if ( winMgr->mIsSetup ) {
        emit winMgr->receivedToplevels( winMgr->mToplevels.values() );
    }
}


const zwayfire_toplevel_manager_v1_listener WQt::ToplevelManager::mTlMgrListener = {
    .new_toplevel = handleNewToplevel,
    .toplevel     = handleToplevel,
    .finished     = handleFinished,
    .done         = handleDone,
};


/**
 * Window Handle Wrapper
 */

WQt::Toplevel::Toplevel( zwayfire_toplevel_v1 *hndl, uint32_t id ) {
    if ( hndl ) {
        mUUID = id;
        mObj  = hndl;

        /* HACK: Details are sent immediately after @hndl object is created */
        zwayfire_toplevel_v1_add_listener( mObj, &mToplevelListener, this );
    }
}


WQt::Toplevel::~Toplevel() {
    zwayfire_toplevel_v1_destroy( mObj );
}


void WQt::Toplevel::setup() {
    /** Setup only if it wasn't setup earlier */
    if ( mIsSetup == false ) {
        mIsSetup = true;

        /** HACK: Emit these signals if they have not yet been emitted */
        if ( pendingTitleChange.length() ) {
            emit titleChanged( pendingTitleChange.at( 0 ), pendingTitleChange.at( 1 ) );
            pendingTitleChange.clear();
        }

        if ( pendingAppIDChange.length() ) {
            emit appIdChanged( pendingAppIDChange.at( 0 ), pendingAppIDChange.at( 1 ) );
            pendingAppIDChange.clear();
        }

        if ( pendingStateChange.length() ) {
            emit stateChanged( pendingStateChange.at( 0 ), pendingStateChange.at( 1 ) );
            pendingStateChange.clear();
        }
    }
}


zwayfire_toplevel_v1 * WQt::Toplevel::get() {
    return mObj;
}


uint32_t WQt::Toplevel::uuid() {
    return mUUID;
}


QString WQt::Toplevel::title() {
    return mTitle;
}


QString WQt::Toplevel::appId() {
    return mAppId;
}


WQt::Toplevel::States WQt::Toplevel::states() {
    return (States)mStates;
}


uint32_t WQt::Toplevel::parent() {
    return mParent;
}


bool WQt::Toplevel::isActive() {
    return mStates & WQt::Toplevel::Active;
}


bool WQt::Toplevel::isMinimized() {
    return mStates & WQt::Toplevel::Minimized;
}


bool WQt::Toplevel::isMaximized() {
    return mStates & WQt::Toplevel::Maximized;
}


bool WQt::Toplevel::isFullscreen() {
    return mStates & WQt::Toplevel::Fullscreen;
}


bool WQt::Toplevel::isPinnedAbove() {
    return mStates & WQt::Toplevel::PinnedAbove;
}


bool WQt::Toplevel::isPinnedBelow() {
    return mStates & WQt::Toplevel::PinnedBelow;
}


bool WQt::Toplevel::isSticky() {
    return mStates & WQt::Toplevel::Sticky;
}


bool WQt::Toplevel::isDemandingAttention() {
    return mStates & WQt::Toplevel::DemandsAttention;
}


void WQt::Toplevel::focus() {
    zwayfire_toplevel_v1_focus( mObj );
}


void WQt::Toplevel::maximize() {
    zwayfire_toplevel_v1_maximize( mObj );
}


void WQt::Toplevel::minimize() {
    zwayfire_toplevel_v1_minimize( mObj );
}


void WQt::Toplevel::restore() {
    zwayfire_toplevel_v1_restore( mObj );
}


void WQt::Toplevel::pinAbove( bool above ) {
    zwayfire_toplevel_v1_set_pin_above( mObj, (int32_t)above );
}


void WQt::Toplevel::pinBelow( bool below ) {
    zwayfire_toplevel_v1_set_pin_below( mObj, (int32_t)below );
}


void WQt::Toplevel::setSticky( bool sticky ) {
    zwayfire_toplevel_v1_set_sticky( mObj, (int32_t)sticky );
}


void WQt::Toplevel::capture() {
    zwayfire_toplevel_v1_capture( mObj );
}


void WQt::Toplevel::startPreview() {
    zwayfire_toplevel_v1_start_preview( mObj );
}


void WQt::Toplevel::stopPreview() {
    zwayfire_toplevel_v1_stop_preview( mObj );
}


void WQt::Toplevel::setMinimizedGeometry( QWindow *window, QRect rect ) {
    wl_surface *surf = WQt::Utils::wlSurfaceFromQWindow( window );

    zwayfire_toplevel_v1_set_minimized_geometry( mObj, surf, rect.x(), rect.y(), rect.width(), rect.height() );
}


void WQt::Toplevel::getProcessInfo() {
    zwayfire_toplevel_v1_get_process_info( mObj );
}


void WQt::Toplevel::close() {
    zwayfire_toplevel_v1_close( mObj );
}


void WQt::Toplevel::kill() {
    zwayfire_toplevel_v1_kill( mObj );
}


void WQt::Toplevel::handleTitleChanged( void *data, struct zwayfire_toplevel_v1 *, const char *oldTitle, const char *newTitle ) {
    WQt::Toplevel *tl = reinterpret_cast<WQt::Toplevel *>(data);

    tl->mTitle = newTitle;

    if ( tl->mIsSetup ) {
        emit tl->titleChanged( oldTitle, newTitle );
    }

    else {
        tl->pendingTitleChange.clear();
        tl->pendingTitleChange << oldTitle << newTitle;
    }
}


void WQt::Toplevel::handleAppIdChanged( void *data, struct zwayfire_toplevel_v1 *, const char *oldId, const char *newId ) {
    WQt::Toplevel *tl = reinterpret_cast<WQt::Toplevel *>(data);

    tl->mAppId = newId;

    if ( tl->mIsSetup ) {
        emit tl->appIdChanged( oldId, newId );
    }

    else {
        tl->pendingAppIDChange.clear();
        tl->pendingAppIDChange << oldId << newId;
    }
}


void WQt::Toplevel::handleStateChanged( void *data, struct zwayfire_toplevel_v1 *, uint32_t oldState, uint32_t newState ) {
    WQt::Toplevel *tl = reinterpret_cast<WQt::Toplevel *>(data);

    tl->mStates = newState;

    if ( tl->mIsSetup ) {
        emit tl->stateChanged( oldState, newState );
    }

    else {
        tl->pendingStateChange.clear();
        tl->pendingStateChange << oldState << newState;
    }
}


void WQt::Toplevel::handleClosed( void *data, struct zwayfire_toplevel_v1 * ) {
    WQt::Toplevel *tl = reinterpret_cast<WQt::Toplevel *>(data);
    emit tl->closed();
}


void WQt::Toplevel::handleParentChanged( void *data, struct zwayfire_toplevel_v1 *, uint32_t old_parent, uint32_t new_parent ) {
    WQt::Toplevel *tl = reinterpret_cast<WQt::Toplevel *>(data);

    tl->mParent = new_parent;

    emit tl->parentChanged( old_parent, new_parent );
}


void WQt::Toplevel::handleGeometryChanged( void *data, struct zwayfire_toplevel_v1 *, int x, int y, int w, int h ) {
    WQt::Toplevel *tl = reinterpret_cast<WQt::Toplevel *>(data);
    emit tl->geometryChanged( QRect( x, y, w, h ) );
}


void WQt::Toplevel::handleViewCaptured( void *data, struct zwayfire_toplevel_v1 *, uint32_t bytes, uint32_t width, uint32_t height ) {
    WQt::Toplevel *tl = reinterpret_cast<WQt::Toplevel *>(data);

    char shm_name[ 24 ] = { 0 };

    sprintf( shm_name, "/shm-view-%d", tl->uuid() );

    key_t key   = ftok( shm_name, tl->uuid() );
    int   shmid = shmget( key, bytes, 0600 | IPC_CREAT );

    uchar *buff = (uchar *)shmat( shmid, (void *)0, 0 );

    QImage img( buff, width, height, QImage::Format_RGBA8888 );
    emit tl->viewCaptured( img.transformed( QTransform().scale( 1, -1 ) ) );

    shmdt( buff );
    shmctl( shmid, IPC_RMID, NULL );
}


void WQt::Toplevel::handleFrameReady( void *data, struct zwayfire_toplevel_v1 *, uint32_t bytes, uint32_t width, uint32_t height ) {
    WQt::Toplevel *tl = reinterpret_cast<WQt::Toplevel *>(data);

    char shm_name[ 24 ] = { 0 };

    sprintf( shm_name, "/shm-view-%d", tl->uuid() );

    key_t key   = ftok( shm_name, tl->uuid() );
    int   shmid = shmget( key, bytes, 0600 | IPC_CREAT );

    uchar *buff = (uchar *)shmat( shmid, (void *)0, 0 );

    QImage img( buff, width, height, QImage::Format_RGBA8888 );
    emit tl->frameReady( img.transformed( QTransform().scale( 1, -1 ) ) );

    shmdt( buff );
    shmctl( shmid, IPC_RMID, NULL );
}


void WQt::Toplevel::handleProcessInfo( void *data, struct zwayfire_toplevel_v1 *, uint32_t pid, uint32_t uid, uint32_t gid ) {
    WQt::Toplevel *tl = reinterpret_cast<WQt::Toplevel *>(data);

    emit tl->processInfo( pid, uid, gid );
}


void WQt::Toplevel::handleDone( void *data, struct zwayfire_toplevel_v1 * ) {
    WQt::Toplevel *tl = reinterpret_cast<WQt::Toplevel *>(data);

    emit tl->done();
}


const zwayfire_toplevel_v1_listener WQt::Toplevel::mToplevelListener = {
    .title_changed    = handleTitleChanged,
    .app_id_changed   = handleAppIdChanged,
    .state_changed    = handleStateChanged,
    .closed           = handleClosed,
    .parent_changed   = handleParentChanged,
    .geometry_changed = handleGeometryChanged,
    .view_captured    = handleViewCaptured,
    .frame_ready      = handleFrameReady,
    .process_info     = handleProcessInfo,
    .done             = handleDone,
};
